package hardware;

import java.util.LinkedList;

public class Execute {
	static LinkedList<Instruction> ins = new LinkedList<Instruction>();

	/**
	 * Executes different type of instructions and calculate the value after the execution time of each instruction.  
	 * @param currentTime 
	 *                  the current time at which the operation is performed.
	 * @throws Exception
	 */
	public static void execute(long currentTime) throws Exception {
		int size = ins.size();
		Instruction instruction;
		int value = 0;
		
		for (; size > 0; size--) { //Change the value in the ROB and the value of the register.
			instruction = ins.removeFirst();
			if(instruction.getType().equals(Instruction.LOAD_WORD)){
				if(instruction.getExecStartTime() == currentTime){
					int address = instruction.getRegB().value + instruction.getImmediate();
					value = Cache.read(address, currentTime, instruction);
					instruction.getRegA().setValue(value);
				} else if(instruction.getCacheEndTime() <= currentTime){
					instruction.setExecEndTime(currentTime);
					ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
					robEntry.setFlag(true);
					robEntry.setValue(instruction.getRegA().getValue());//change value of logical register
					instruction.setCommitStartTime(currentTime);
					//Processor.updateReservationStations(instruction.getID(), instruction.getRegA().getValue()); //Update the registers in the reservation stations and mark them as ready
					instruction.getRegA().setReady(true);
					Commit.instructions.add(instruction);
					freeReservationStation(instruction);
					freeFunctionalUnit(instruction.getID());
				    continue;	
				}
				ins.addLast(instruction);
				continue;
			}else if(instruction.getType().equals(Instruction.STORE_WORD)){
				if(instruction.getExecStartTime() == currentTime){
					int address = instruction.getRegB().value + instruction.getImmediate();
					Cache.write(address,instruction.getRegA().value, currentTime, instruction);
				} else if(instruction.getCacheEndTime() <= currentTime){
					instruction.setExecEndTime(currentTime);
					instruction.setCommitStartTime(currentTime);
					instruction.setCommitEndTime(currentTime);
					freeReservationStation(instruction);
					freeFunctionalUnit(instruction.getID());
					Commit.instructions.add(instruction);
					ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
					robEntry.setFlag(true);
				    continue;	
				}
				ins.addLast(instruction);
				continue;
			}else if (instruction.getExecStartTime() + instruction.getExecutionTime() <= currentTime+1) {
				if(instruction.getType().equals(Instruction.BRANCH_IF_EQUAL)){
					instruction.setExecEndTime(currentTime);
					if(instruction.getRegA().getValue() == instruction.getRegB().getValue()){
						//System.out.println("hobaaaaaaaaaaaaaaaaaaaaaaaa");
						while(!Fetch.instructions.isEmpty()){
							Fetch.instructions.remove();
						}
						while(!Decode.instructions.isEmpty()){
						Decode.instructions.remove();
						}
						while(!Dispatch.instructions.isEmpty()){
							Dispatch.instructions.remove();
						}
						Commit.instructions.add(instruction);
						Fetch.PC = instruction.getID() + instruction.getImmediate() + 1;
						Issue.removeInstructionsAfterId(instruction.getID());
						size-=removeInstructionsAfterId(instruction.getID());
						Processor.removeFromReservationAfterId(instruction.getID());
						Processor.removeFromRobAfterId(instruction.getID());
						freeFunctionalUnitAfterId(instruction.getID());
						freeReservationStation(instruction);
						freeFunctionalUnit(instruction.getID());
						instruction.setCommitStartTime(currentTime);
						instruction.setCommitEndTime(currentTime);						
					}else{
						freeReservationStation(instruction);
						instruction.setCommitEndTime(currentTime);
						instruction.setCommitStartTime(currentTime);
						Commit.instructions.add(instruction);
					}
					ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
					robEntry.setFlag(true);
				}else if(instruction.getType().equals(Instruction.JUMP)){
					instruction.setExecEndTime(currentTime);
						while(!Fetch.instructions.isEmpty()){
							Fetch.instructions.remove();
						}
						while(!Decode.instructions.isEmpty()){
						Decode.instructions.remove();
						}
						while(!Dispatch.instructions.isEmpty()){
							Dispatch.instructions.remove();
						}
						Commit.instructions.add(instruction);
						Issue.removeInstructionsAfterId(instruction.getID());
						size-=removeInstructionsAfterId(instruction.getID());
						Processor.removeFromReservationAfterId(instruction.getID());
						Processor.removeFromRobAfterId(instruction.getID());
						freeFunctionalUnitAfterId(instruction.getID());
						freeReservationStation(instruction);
						freeFunctionalUnit(instruction.getID());
						Fetch.PC = instruction.getRegA().value+ instruction.getID() + instruction.getImmediate() + 1 ;
						instruction.setCommitStartTime(currentTime);
						ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
						robEntry.setFlag(true);						
				
				}else if(instruction.getType().equals(Instruction.JUMP_AND_LINK)){
					instruction.setExecEndTime(currentTime);
					while(!Fetch.instructions.isEmpty()){
						Fetch.instructions.remove();
					}
					while(!Decode.instructions.isEmpty()){
					Decode.instructions.remove();
					}
					while(!Dispatch.instructions.isEmpty()){
						Dispatch.instructions.remove();
					}
					Commit.instructions.add(instruction);
					Issue.removeInstructionsAfterId(instruction.getID());
					size-=removeInstructionsAfterId(instruction.getID());
					instruction.getRegA().setValue(instruction.getID()+1);
					//Processor.updateReservationStations(instruction.getID(), instruction.getID()+1); //Update the registers in the reservation stations and mark them as ready
					instruction.getRegA().setReady(true);
					ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
					robEntry.setFlag(true);
					robEntry.setValue(instruction.getID()+1);//change value of logical register
					Processor.removeFromReservationAfterId(instruction.getID());
					Processor.removeFromRobAfterId(instruction.getID());
					freeFunctionalUnitAfterId(instruction.getID());
					freeReservationStation(instruction);
					freeFunctionalUnit(instruction.getID());
					Fetch.PC = instruction.getRegB().value-(Memory.memorySize-Memory.programSize);
					}else if(instruction.getType().equals(Instruction.RETURN)){
				instruction.setExecEndTime(currentTime);
				while(!Fetch.instructions.isEmpty()){
					Fetch.instructions.remove();
				}
				while(!Decode.instructions.isEmpty()){
				Decode.instructions.remove();
				}
				while(!Dispatch.instructions.isEmpty()){
					Dispatch.instructions.remove();
				}
				Commit.instructions.add(instruction);
				Issue.removeInstructionsAfterId(instruction.getID());
				size-=removeInstructionsAfterId(instruction.getID());
				ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
				robEntry.setFlag(true);
				//Processor.rob.remove(Processor.getROBEntry(instruction.getID()));
				Processor.removeFromReservationAfterId(instruction.getID());
				Processor.removeFromRobAfterId(instruction.getID());
				freeFunctionalUnitAfterId(instruction.getID());
				freeReservationStation(instruction);
				freeFunctionalUnit(instruction.getID());
				Fetch.PC = instruction.getRegA().value;
				instruction.setCommitStartTime(currentTime);
				//instruction.setCommitEndTime(currentTime);
		} else{
				instruction.setExecEndTime(currentTime);
				value = calculateResult(instruction);
				instruction.getRegA().setValue(value); //change value of physical register
				ROBEntry robEntry = Processor.getROBEntry(instruction.getID());
				robEntry.setFlag(true);
				robEntry.setValue(value);//change value of logical register
				instruction.setCommitStartTime(currentTime);
				//Processor.updateReservationStations(instruction.getID(), value); //Update the registers in the reservation stations and mark them as ready
				instruction.getRegA().setReady(true);
				freeReservationStation(instruction);
				freeFunctionalUnit(instruction.getID());
				Commit.instructions.add(instruction);
				}
			} else {
				ins.addLast(instruction);
			}
		
		}
	}
	
	/**
	 * free Functional unit when instruction finishes execution.
	 * @param id
	 *          the id of the instruction inside the functional unit.
	 */
	private static void freeFunctionalUnit(int id) {
		for(int i=0;i<Processor.functionalUnits.size();i++){
		    if(Processor.functionalUnits.get(i).id == id){
		    	Processor.functionalUnits.get(i).empty = true;
		    	Processor.functionalUnits.get(i).reserved = false;
		    }
		}
	}
	
	/**
	 * free functional units with id's after certain id used when instruction is any type of branch.
	 * @param Id
	 *         id of the branch instruction.
	 */
	private static void freeFunctionalUnitAfterId(int Id) {
		for(int i=0;i<Processor.functionalUnits.size();i++){
		    if(Processor.functionalUnits.get(i).id > Id){
		    	Processor.functionalUnits.get(i).empty = true;
		    	Processor.functionalUnits.get(i).reserved = false;
		    }
		}
	}

	/**
	 * calculating result of different type of instructions.
	 * @param ins
	 *           takes the instruction needed to be executed.
	 * @return
	 *         the result of the execution of the instruction.
	 * @throws Exception
	 */
	private static int calculateResult(Instruction ins) throws Exception{
		int value = 0;					
		if(ins.getType().equals(Instruction.ADD)){
			value =  ins.getRegB().value + ins.getRegC().value;
		} else if(ins.getType().equals(Instruction.ADDI)){
			value =  ins.getRegB().value + ins.getImmediate();
		} else if(ins.getType().equals(Instruction.DIV)){
			value =  ins.getRegB().value / ins.getRegC().value;
		} else if(ins.getType().equals(Instruction.MULT)){
			value =  ins.getRegB().value * ins.getRegC().value;
		} else if(ins.getType().equals(Instruction.NAND)){
			value =  ~(ins.getRegB().value & ins.getRegC().value);
		} else {
			throw new Exception("Undefined operation", new Throwable());
		}
		return value;
	}
	
	/**
	 * add the instruction to the list of instructions in the execution stage.
	 * @param instruction
	 *                   instruction that should start execution.
	 * @param currentTime
	 *                   the current time at which the operation is performed.
	 */
	public static void addInstructionToExecuteStage(Instruction instruction, long currentTime){
		instruction.setExecStartTime(currentTime+1);
		ins.add(instruction);
	}
	
	/**
	 * free the reservationStaion when instruction finishes execution. 
	 * @param instruction
	 *                   instruction that finished the execution.
	 */
	public static void freeReservationStation(Instruction instruction){
		for(int i=0;i<Processor.reservationStations.size();i++){
			if(Processor.reservationStations.get(i).getIns()!=null){
			if(Processor.reservationStations.get(i).getIns().equals(instruction)){
				Processor.reservationStations.get(i).setReserve(false);
				Processor.reservationStations.get(i).setFull(false);
			}
		}
	}
	}
	
	/**
	 * remove instructions with id's after certain id from execution stage used when instruction is any type of branch.
	 * @param id
	 *          id of the branch instruction.
	 * @return
	 *        number of instructions removed.
	 */
	public static int removeInstructionsAfterId(int id) {
		LinkedList<Instruction> instructions = new LinkedList<Instruction>();
		for (int i = 0; i < ins.size(); i++) {
			if (ins.get(i).getID() > id) {
				instructions.add(ins.get(i));
			}
		}
		ins.removeAll(instructions);
		return instructions.size();
	}
}
